package ru.amse.stroganova.io;

import java.awt.Point;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import ru.amse.stroganova.graph.AbstractGraph;
import ru.amse.stroganova.graph.DirectedGraph;
import ru.amse.stroganova.graph.Edge;
import ru.amse.stroganova.graph.UndirectedGraph;
import ru.amse.stroganova.graph.Vertex;
import ru.amse.stroganova.presentation.GraphPresentation;

/**
 * Implements parsing graphs and presentations from xml file.
 * 
 * @author Dasha Stroganova
 *
 */
public class GraphLoader {

	private final File file;
	
	private AbstractGraph graph;
	
	private GraphPresentation presentation;
	
	private Document document;
	
	private Map<String, Vertex> verticesByIndex;
	
	private boolean isDirected;
	
	private static final String JAXP_SCHEMA_LANGUAGE =
	    "http://java.sun.com/xml/jaxp/properties/schemaLanguage";

	private static final String W3C_XML_SCHEMA =
	    "http://www.w3.org/2001/XMLSchema";
	
	private static final String JAXP_SCHEMA_SOURCE =
	    "http://java.sun.com/xml/jaxp/properties/schemaSource";

	private static final String MY_SCHEMA = "ru/amse/stroganova/io/schema/graph.xsd";
	
	private final ErrorHandler errorHandler = new ErrorHandler() {

		public void error(SAXParseException exception) throws SAXException {
			errorMessages.add(exception.getMessage());
		}

		public void fatalError(SAXParseException exception) throws SAXException {
			throw new SAXException(exception.getMessage());
		}

		public void warning(SAXParseException exception) throws SAXException {
			errorMessages.add(exception.getMessage());
		}
		
	};
	
	private final List<String> errorMessages;
	
	/**
	 * Creates new GraphParser for given file.
	 * 
	 * @param file
	 */
	public GraphLoader(File file) {
		this.file = file;
		errorMessages = new ArrayList<String>();
	}
	
	/**
	 * Validates file according to xml schema. Descriptions of errors or warnings occured can be retrieved using getErrorMessages() method.
	 * It is not recommended to continue parsing if file is not valid.
	 *
	 * @throws ParserConfigurationException 
	 * @throws SAXException 
	 * @throws IOException 
	 * @throws IllegalArgumentException 
	 * @throws URISyntaxException 
	 *
	 */
	private void validate() throws ParserConfigurationException, SAXException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
		factory.setAttribute(JAXP_SCHEMA_SOURCE, new File(ClassLoader
					.getSystemResource(MY_SCHEMA).getFile()));
		
		
		DocumentBuilder builder = factory.newDocumentBuilder();
		builder.setErrorHandler(errorHandler);
		document = builder.parse(file);
		if (getErrorMessages().size() != 0) {
			throw new SAXException();
		}
	}
	
	/**
	 * Parses graph from file.
	 * @throws IOException 
	 * @throws SAXException 
	 * @throws ParserConfigurationException 
	 *
	 */
	public void parseGraph() throws ParserConfigurationException, SAXException, IOException {
		validate();
		
		Element root = document.getDocumentElement();
		Node graphNode = root.getElementsByTagName("model").item(0);
		NodeList vertices = root.getElementsByTagName("vertex");
		NodeList edges = root.getElementsByTagName("edge");
		
		if ("yes".equals(graphNode.getAttributes().getNamedItem("directed").getNodeValue())) {
			graph = new DirectedGraph();
			isDirected = true;
		} else {
			graph = new UndirectedGraph();
			isDirected = false;
		}
		
		parseGraphElements(vertices, edges);
	}
	
	/**
	 * Parses graph presentation from file.
	 * @throws IOException 
	 * @throws SAXException 
	 * @throws ParserConfigurationException 
	 */
	public void parsePresentation() throws ParserConfigurationException, SAXException, IOException {
		parseGraph();
		
		Element root = document.getDocumentElement();
		Node presentationNode = root.getElementsByTagName("presentation").item(0);
		NodeList centers = root.getElementsByTagName("center");
		
		if ("yes".equals(presentationNode.getAttributes().getNamedItem("weighted").getNodeValue())) {
			presentation = new GraphPresentation(graph, isDirected, true);
		} else {
			presentation = new GraphPresentation(graph, isDirected, false);
		}
		
		parseVertexCenters(centers);
	}
	
	private void parseGraphElements(NodeList vertices, NodeList edges) {
		verticesByIndex = new HashMap<String, Vertex>();
		for (int i = 0; i < vertices.getLength(); i++) {
			Vertex vertex = new Vertex();
			String id = vertices.item(i).getAttributes().getNamedItem("id").getNodeValue();
			graph.addVertex(vertex);
			verticesByIndex.put(id, vertex);
		}
		for (int i = 0; i < edges.getLength(); i++) {
			String sourceID = edges.item(i).getAttributes().getNamedItem("sourceID").getNodeValue();
			String destinationID = edges.item(i).getAttributes().getNamedItem("destinationID").getNodeValue();
			Integer weight = Integer.parseInt(edges.item(i).getAttributes().getNamedItem("weight").getNodeValue());
			Edge edge = new Edge(verticesByIndex.get(sourceID), verticesByIndex.get(destinationID), weight);
			graph.addEdge(edge);
		}
	}
	
	private void parseVertexCenters(NodeList centers) {
		for (int i = 0; i < centers.getLength(); i++) {
			String vertexID = centers.item(i).getAttributes().getNamedItem("vertexID").getNodeValue();
			Integer centerX = Integer.parseInt(centers.item(i).getAttributes().getNamedItem("x").getNodeValue());
			Integer centerY = Integer.parseInt(centers.item(i).getAttributes().getNamedItem("y").getNodeValue());
			presentation.getPresentationForVertex(verticesByIndex.get(vertexID)).setCenter(new Point(centerX, centerY));
		}
	}
	
	/**
	 * Returns parsed graph. If graph hasn't yet been parsed new IllegalStateExecption is thrown.
	 * 
	 * @return parsed graph
	 */
	public AbstractGraph getGraph() {
		if (graph == null) {
			throw new IllegalStateException("Graph hasn't been parsed yet!");
		}
		return graph;
	}
	
	/**
	 * Returns parsed graph presentation. If graph presentation hasn't yet been parsed new IllegalStateExecption is thrown.
	 * 
	 * @return parsed graph presentation
	 */
	public GraphPresentation getGraphPresentation() {
		if (presentation == null) {
			throw new IllegalStateException("Presentation hasn't been parsed yet!");
		}
		return presentation;
	}
	
	/**
	 * Returns list of error messages.
	 * 
	 * @return list of error messages
	 */
	public List<String> getErrorMessages() {
		return errorMessages;
	}
}
